/*!
 * \file Mem.cpp
 * \brief Memory module.
 * \author Nus
 * \date 2008/02/17, 21:37
 */

#include "Debug.h"
#include "Mem.h"

#define MEM_MAGIC_U32 '_Mem'

// System memory handle.
sMemoryHandle SysMemHandle;
sMemoryHandle* CurrentMemHandle = NULL;

// Basic memory allocator.
void* memAlloc(size_t size, s32 align, const s8* file_loc, s32 line)
{
  if(!CurrentMemHandle)
    CurrentMemHandle = &SysMemHandle;
  return memAllocFromHandle(CurrentMemHandle, size, align, file_loc, line);
}

// Basic memory deallocator.
void memFree(void* addr)
{
  ASSERT_C(addr != NULL);
  sMemoryHandle::sMemHeader* mem_header =
      static_cast< sMemoryHandle::sMemHeader* >(addr);
  mem_header--;
  ASSERT_C(mem_header->magic == MEM_MAGIC_U32);
  {
    AutoMutex mtx(&mem_header->handle->mutex);
    if(mem_header->prev) {
      // Detach from chain.
      mem_header->prev->next = mem_header->next;
    } else {
      // Change the first entry of memory chain.
      mem_header->handle->mem_chain = mem_header->next;
    }
    if(mem_header->next)
      mem_header->next->prev = mem_header->prev;
    // If the last entry...
    if(mem_header->handle->last == mem_header)
      mem_header->handle->last = mem_header->prev;
    mem_header->handle->used_memory -= mem_header->allocated;  
    free(mem_header->alloc_addr);
  }
}

// Basic memory allocator.
void* memAllocFromHandle(sMemoryHandle* p_mh, size_t size, s32 align,
                         const s8* file_loc, s32 line)
{
  if(!p_mh)
    return NULL;
  if(size == 0)
    return NULL;
  AutoMutex mtx(&p_mh->mutex);
  size_t alloc_size = size + sizeof(sMemoryHandle::sMemHeader) + align;
  void* ret = malloc(alloc_size);
  if(ret) {
    u64 addr = ret + sizeof(sMemoryHandle::sMemHeader);
    if(align > 1) {
      addr -= addr % align;
      addr += align;
    }
    // Fill header.
    sMemoryHandle::sMemHeader* header =
        reinterpret_cast< sMemoryHandle::sMemHeader* >(addr);
    header--;
    header->handle = p_mh;
    header->magic = MEM_MAGIC_U32;
    header->tag = CalcAutoTag(p_mh);
    header->allocated = alloc_size;
    header->alloc_addr = ret;
    header->mem_size = size;
    header->file_loc = file_loc;
    header->line_loc = line;
    header->addr = reinterpret_cast< void* >(addr);
    // Add to chain.
    if(p_mh->last) {
      p_mh->last->next = header;
      header->prev = p_mh->last;
      header->next = NULL;
      p_mh->last = header;
    } else {
      header->prev = header->next = NULL;
      p_mh->mem_chain = header;
      p_mh->last = header;
    }
    p_mh->used_memory += header->allocated;
    return header->addr;
  }
  return NULL;
}

// Calculate auto tag.
u32 CalcAutoTag(sMemoryHandle* p_mh)
{
  ASSERT_C(p_mh != NULL);
  u32 ret = p_mh->auto_tag;
  p_mh->auto_tag++;
  if(p_mh->auto_tag == 0xffffffff)
    p_mh->auto_tag = 0;
  return ret;
}

// Dump memory info.
void DumpInfo(const sMemoryHandle* p_mh)
{
#ifndef _NDEBUG
  if(p_mh == NULL)
    p_mh = CurrentMemHandle;
  if(p_mh == NULL)
    return;
  AutoMutex mtx(&p_mh->mutex);
  const sMemoryHandle::sMemHeader* p_current = p_mh->mem_chain;
  while(p_current) {
    LOG("Tag-%d: %p (%d)\n",
        p_current->tag, p_current->addr, p_current->mem_size);
    p_current = p_current->next;
  }
#endif
}

// Dump memory info (detailed version).
void DumpInfoEx(const sMemoryHandle* p_mh)
{
#ifndef _NDEBUG
  if(p_mh == NULL)
    p_mh = CurrentMemHandle;
  if(p_mh == NULL)
    return;
  AutoMutex mtx(&p_mh->mutex);
  const sMemoryHandle::sMemHeader* p_current = p_mh->mem_chain;
  while(p_current) {
    if(p_current->file_loc)
      LOG("Tag-%d: %s:%d, ", p_current->tag, p_current->file_loc,
          p_current->line_loc);
    else
      LOG("Tag-%d: Unknown location, ", p_current->tag);
    LOG("E: %p (%d), ", p_current->alloc_addr,
        p_current->allocated);
    LOG("A: %p (%d)\n", p_current->addr,
        p_current->mem_size);
    p_current = p_current->next;
  }
#endif
}

// Get current memory handle.
sMemoryHandle* GetCurrentMemoryHandle(void)
{
  return CurrentMemHandle;
}

// Get current memory handle.
sMemoryHandle* SetCurrentMemoryHandle(sMemoryHandle* handle)
{
  sMemoryHandle* ret = CurrentMemHandle;
  CurrentMemHandle = handle;
  return ret;
}
